
local jass = require 'jass.common'
local runtime = require 'jass.runtime'
local dbg = require 'jass.debug'

local sound = {}
setmetatable(sound,sound)
ac.sound = sound

sound.allsounds = {}
sound.pool = {}

function sound:__tostring()
    return ('音效[%s] - %s'):format(self.path)
end

-- 创建一个音效 路径,循环播放,3D音效,超出范围时停止,淡入率,淡出率,效果名
function sound.create(label, info)
    if sound.pool[label] then 
        return sound.pool[label]
    end
    local snd = setmetatable(info, sound)
    local handle = jass.CreateSound(snd.path, snd.loop, snd._3D, snd.outofrange, snd.fadein, snd.fadeout, sound.EAX_LIST[snd.EAX] or '')
    if handle == 0 then 
        log.error("创建音效失败", snd)
        return 
    end 
    snd.label = label
    jass.SetSoundParamsFromLabel(handle, label)
    if not rawget(snd, 'duration') then
        snd.duration = jass.GetSoundFileDuration(snd.path)
    end 
    dbg.handle_ref(handle)
    snd.handle = handle
    snd.gchash = handle
    dbg.gchash(snd, handle)
    sound.allsounds[handle] = snd
    sound.pool[label] = snd
    snd:init()
    return snd
end 

function sound.init()

    -- 错误
    sound.create("InterfaceError", {
        path = "Sound\\Interface\\Error.wav",
        _3D = false,
        loop = false,
        outofrange = false,
        fadein = 10,
        fadeout = 10,
        EAX = "",
        duration = 614,
    })

    -- 点击
    sound.create("InterfaceClick", {
        path = "Sound\\Interface\\MouseClick1.wav",
        _3D = false,
        loop = false,
        outofrange = false,
        fadein = 10,
        fadeout = 10,
        EAX = "",
        duration = 239,
    })
        
    -- 金币
    sound.create("金币", {
        path = "Abilities\\Spells\\Items\\ResourceItems\\ReceiveGold.wav",
        _3D = true,
        loop = false,
        outofrange = true,
        fadein = 10,
        fadeout = 10,
        EAX = "SpellsEAX",
        duration = 589,
    })

    -- 木材
    sound.create("木材", {
        path = "Abilities\\Spells\\Items\\ResourceItems\\BundleOfLumber.wav",
        _3D = true,
        loop = false,
        outofrange = true,
        fadein = 10,
        fadeout = 10,
        EAX = "SpellsEAX",
        duration = 589,
    })


    -- 标记
    sound.create("Hint", {
        path = "Sound\\Interface\\Hint.wav",
        _3D = false,
        loop = false,
        outofrange = false,
        fadein = 10,
        fadeout = 10,
        EAX = "SpellsEAX",
        duration = 2006,
    })

    -- found
    sound.create("GameFound", {
        path = "Sound\\Interface\\GameFound.wav",
        _3D = false,
        loop = false,
        outofrange = false,
        fadein = 10,
        fadeout = 10,
        EAX = "SpellsEAX",
        duration = 7488,
    })

    -- 危险
    sound.create("Warning", {
        path = "Sound\\Interface\\Warning.wav",
        _3D = false,
        loop = false,
        outofrange = false,
        fadein = 10,
        fadeout = 10,
        EAX = "SpellsEAX",
        duration = 1904,
    })

end

function sound:__call(name)
    if sound.pool[name] then 
        return sound.pool[name]
    end
end 

function ac.music(musicPath)
    jass.PlayMusic(musicPath)
end 

function sound:error()
    sound.pool['InterfaceError']:play()
end

function sound:click()
    sound.pool['InterfaceClick']:play()
end

function sound:hint()
    sound.pool['Hint']:play()
end

function sound:warning()
    sound.pool['Warning']:play()
end

function sound:found()
    sound.pool['GameFound']:play()
end

function sound.convert(label, handle)
    if sound.pool[label] then
        return sound.pool[label]
    end
    local snd = setmetatable({
        handle = handle,
        label = label,
    }, sound)
    dbg.handle_ref(handle)
    snd.handle = handle
    snd.gchash = handle
    dbg.gchash(snd, handle)
    sound.allsounds[handle] = snd
    sound.pool[label] = snd
    return snd
end

--结构
local mt = {}
sound.__index = mt

--音效常量
sound.EAX_LIST = {
    ['没有'] = "",
    ['魔法'] = "SpellsEAX",
    ['投射物'] = "MissilesEAX",
    ['默认'] = "DefaultEAXON",
    ['战斗'] = "CombatSoundsEAX",
    ['战鼓'] = "KotoDrumsEAX",
    ['英雄语音'] = "HeroAcksEAX",
    ['装饰物'] = "DoodadsEAX",
}

--通道常量
sound.CHANNEL_LIST = {
    ['普通'] = 0,
    ['单位选择'] = 1, --默认
    ['单位确认'] = 2, 
    ['单位移动'] = 3, 
    ['火焰'] = 4, 
    ['动画'] = 5, 
    ['循环环境'] = 6, 
    ['音乐'] = 7, 
    ['错误'] = 8, 
    ['战斗'] = 9, 
    ['单位准备'] = 10, 
    ['出生'] = 11, 
    ['建筑'] = 12, 
    ['用户界面'] = 13, 
    ['循环移动'] = 14, 
}

-- 类型
mt.type = 'sound'

-- 句柄
mt.handle = jass.ConvertUnitState(0)

-- 标签 可以理解为音效的名字,有且必须要有一个名字且不要重复
mt.label = nil 

-- 播放时间 integer /ms毫秒
mt.duration = 1000

-- 是否循环播放
mt.loop = false

-- 是否是3D音效 注意3D音效需要将源文件设置为单声道
mt._3D = false

-- 超出范围是否自动停止
mt.outofrange = false

-- 淡入率 integer /ms毫秒
mt.fadein = 10

--淡出率 integer /ms毫秒
mt.fadeout = 20

-- 音效音量 integer
mt.volume = 127

-- 音效速率 real 
mt.pitch = 1.0

-- 路径
mt.path = 'sound.wav'

-- 效果
mt.EAX = '没有'

-- 通道
mt.channel = '单位选择'

-- 截断距离(超过听不见)
mt.cutoff = 100000.0

-- base api
function mt:init()
    self:set_duration(self.duration)
    self:set_channel(self.channel)
    self:set_volume(self.volume)
    self:set_pitch(self.pitch)
    return self 
end 

-- 判断音效是否为3D音效
function mt:is3D() 
    return self._3D == true 
end 

-- 设置音效持续时间 ms
function mt:set_duration(duration)
    self.duration = math.floor(duration)
    jass.SetSoundDuration(self.handle, self.duration)
end 

-- 设置音效对象播放通道
function mt:set_channel(channel)
    self.channel = channel
    jass.SetSoundChannel(self.handle, sound.CHANNEL_LIST[self.channel] or 1)
end 

-- 设置音效音量
function mt:set_volume(volume)
    self.volume = math.floor(volume)
    jass.SetSoundVolume(self.handle, self.volume)
end 

-- 设置音效速率
function mt:set_pitch(pitch)
    self.pitch = pitch
    jass.SetSoundPitch(self.handle, self.pitch)
end 

-- 获取音效是否处于播放状态
function mt:is_playing()
    return jass.GetSoundIsPlaying(self.handle)
end 

-- 设置播放时间点 该函数只能在 播放状态下调用
function mt:set_millisecs(millisecs)
    if not self:is_playing() then return end
    jass.SetSoundPlayPosition(self.handle, millisecs)
end

---停止播放音效
---@param fadeout '是否淡出'
function mt:stop(fadeout)
    jass.StopSound(self.handle, false, fadeout and true or false)
end

---播放音效
---@param fadeout '是否淡出'
function mt:play(fadeout)
    jass.StopSound(self.handle, false, fadeout and true or false)
    jass.StartSound(self.handle)
end


-----------3Dapi 该类型的接口只适用于3D音效-----------

-- 设置音效的最大截断距离,超过该距离后音效会听不见
function mt:set_distance_cutoff(cutoff)
    self.cutoff = cutoff
    jass.SetSoundDistanceCutoff(self.handle, self.cutoff)
end 

-- 设置音效对象听觉距离 必须设置该项属性后下面的api才会有效
function mt:set_distance(mindist, maxdist)
    if self:is3D() then 
        jass.SetSoundDistances(self.handle, mindist, maxdist)
    end
    return self
end

---设置音效对象的发声角度和音量大小
---@param inside '内发声角度，即声音最佳听取范围的角度'
---@param outside '外发声角度，即声音可以被听到的最大范围角度'
---@param volume '声音在外发声角度范围内的音量大小
function mt:set_cone_angles(inside, outside, volume)
    if self:is3D() then 
        jass.SetSoundConeAngles(self.handle, inside or 0, outside or 0, volume or 127)
    end
    return self
end

---让音效对象在播放时沿着指定方向传播声音
---@param x '音效对象发声方向的 X 轴坐标'
---@param y '音效对象发声方向的 Y 轴坐标'
---@param z '音效对象发声方向的 Z 轴坐标'
function mt:set_cone_orientation(x, y, z)
    if self:is3D() then 
        jass.SetSoundConeOrientation(self.handle, x or 0, y or 0, z or 0)
    end
    return self
end 

--- 在单位身上播放音效
function ac.unit.__index:play_sound(name, volume, fadeout)
    if self.removed then return end
    local snd = sound.pool[name]
    if not snd then
        log.error('音效播放失败', name)
        return
    end
    if volume then 
        snd:set_volume(volume)
    end
    jass.StopSound(snd.handle, false, fadeout and true or false)
    jass.AttachSoundToUnit(snd.handle, self.handle)
    jass.StartSound(snd.handle)
    return snd
end 

--- 对点播放音效
function ac.point.__index:play_sound(name, volume, fadeout)
    local snd = sound.pool[name]
    if not snd then
        log.error('音效播放失败', name)
        return
    end
    if volume then 
        snd:set_volume(volume)
    end
    local x,y,z = self:get()
    jass.StopSound(snd.handle, false, fadeout and true or false)
    jass.SetSoundPosition(snd.handle, x, y, z)
    jass.StartSound(snd.handle)
    return snd
end

return sound

